﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using CoreEx.Database.Metadata;
using LinFu.IoC.Configuration;
using CoreEx.Common.Extensions;
namespace CoreEx.Database.MetaData.Implementation
{
    public abstract class TypeGeneratorBase : ITypeGenerator
    {
        public abstract string Generate(TableInfo tableInfo);        
        public virtual string Namespace { get; set;}

        protected virtual string GetClassName(TableInfo tableInfo)
        {
            return GetTableName(tableInfo).ToSingular();
        }

        protected virtual string GetTableName(TableInfo tableInfo)
        {
            return tableInfo.TableName.Replace(" ", "");   
        }


        protected static string GetDataTypeName(Type type)
        {
            
            if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                return (ToIntegralName(Nullable.GetUnderlyingType(type))+ "?");
            }

            return ToIntegralName(type);
            
        }


        protected static string ToIntegralName(Type type)
        {
            switch (type.Name)
            {
                case "Boolean":
                    return "bool";
                case "Single":
                    return "float";
                case "Int16":
                    return "short";
                case "UInt16":
                    return "ushort";
                case "Int32":
                    return "int";
                case "UInt32":
                    return "uint";
                case "Int64":
                    return "long";
                case "UInt64":
                    return "ulong";
                case "DateTime" :
                    return type.Name;
                case "XElement":
                    return type.Name;
            }
            return type.Name.ToLower();
        }

    }

    [Implements(typeof(ITypeGenerator),ServiceName = "ClassGenerator")]
    public class ClassGenerator : TypeGeneratorBase
    {
        
        
        private string ToMemberName(string value)
        {
            return string.Format("_{0}", value.Substring(0,1).ToLower() + value.Substring(1));
        }


        public override string Generate(TableInfo tableInfo)
        {
           
            

            
            var sb = new StringBuilder();

            sb.AppendLine(0, "using System;");
            sb.AppendLine(0, "using System.Collections.Generic;");
            sb.AppendLine(0, "using System.Runtime.Serialization;");            
            sb.AppendLine(0, "using LinFu.IoC.Configuration;");            
            sb.AppendLine();
            sb.AppendLine(0, "namespace {0}", Namespace);
            sb.AppendLine(0, "{");
            sb.AppendLine(1, "[Implements(typeof(I{0}))]", GetClassName(tableInfo));
            sb.AppendLine(1, "[Serializable]");            
            sb.AppendLine(1, "public partial class {0} : I{0}", GetClassName(tableInfo));
            sb.AppendLine(1, "{");
            

            var foreignKeyColumns =
                tableInfo.ForeignKeys.SelectMany(fk => fk.Columns).Select(fkc => fkc.ForeignColumnInfo).Except(tableInfo.PrimaryKey.Columns);

            var childRelations = tableInfo.Relations.Select(r => r.ForeignKey).Except(tableInfo.ForeignKeys).GroupBy( c => c.Table);
            sb.AppendLine();
            sb.AppendLine(2,"#region Private Members");
            sb.AppendLine();

            
            foreach (var childRelation in childRelations)
            {
                if (childRelation.Count() == 1)
                {
                    //sb.AppendLine(2, "[DataMember]");
                    sb.AppendLine(2, "private IList<I{0}> {1} = new List<I{0}>();",
                                  GetClassName(childRelation.Key),
                                  ToMemberName(GetTableName(childRelation.Key)));
                }
            }
            sb.AppendLine();
            sb.AppendLine(2, "#endregion");
            sb.AppendLine();

            //Non-relational properies
            foreach (var column in tableInfo.Columns.Values.Except(foreignKeyColumns).OrderBy(c => c.OrdinalPosition))
            {
                //sb.AppendLine(2,"[DataMember]");
                if (column.ColumnName == GetClassName(tableInfo))
                    sb.AppendLine(2, "public {0} {1}{2} {{ get; set; }}", GetDataTypeName(column.DataType), column.ColumnName,column.OrdinalPosition);
                else
                    sb.AppendLine(2, "public {0} {1} {{ get; set; }}", GetDataTypeName(column.DataType), column.ColumnName);
            }

                        
            foreach (var childRelation in childRelations)
            {
                if (childRelation.Count() == 1)
                {                    
                    sb.AppendLine(2, "public IList<I{0}> {1} ", GetClassName(childRelation.Key),
                                  GetTableName(childRelation.Key));
                    sb.AppendLine(2, "{");
                    sb.AppendLine(3, "get {{ return {0}; }}", ToMemberName(GetTableName(childRelation.Key)));
                    sb.AppendLine(2, "}");
                }
            }

            if (tableInfo.TableName == "Currencies")
                Console.WriteLine("");
            foreach (var foreignKey in tableInfo.ForeignKeys)
            {
                //handle singular keys
                if (foreignKey.Columns.Count == 1)
                {
                    var foreignKeyColumnInfo = foreignKey.Columns.First();
                    var propertyName = foreignKeyColumnInfo.ForeignColumnInfo.ColumnName;
                    var primaryKeyColumnName = foreignKey.Columns.First().PrimaryColumnInfo.ColumnName;                    
                    if (propertyName != primaryKeyColumnName)
                    {
                        if (propertyName.Contains(primaryKeyColumnName))
                            propertyName = propertyName.Replace(primaryKeyColumnName, GetClassName(foreignKey.PrimaryTable));
                        else
                            propertyName = GetClassName(foreignKey.PrimaryTable);
                    }
                    else
                    {
                        propertyName = GetClassName(foreignKey.PrimaryTable);
                        if (tableInfo == foreignKey.PrimaryTable)
                            propertyName = string.Format("Parent{0}", propertyName);
                    }



                    var propertyString = string.Format("public I{0} {1} {{ get; set;}}",GetClassName(foreignKey.PrimaryTable),propertyName);
                    //sb.AppendLine(2, "[DataMember]");
                    sb.AppendLine(2, propertyString);
                }
                else
                {
                    //sb.AppendLine(2, "[DataMember]");
                    sb.AppendLine(2, "public I{0} {0} {{ get; set;}}", GetClassName(foreignKey.PrimaryTable));
                }
            }


            ////Parent relations
            //var parentTables = tableInfo.ForeignKeys.Select(fk => fk.PrimaryTable).Distinct();
            //foreach (var parentTable in parentTables)
            //{

            //    if (parentTable == tableInfo)
            //        sb.AppendLine(2, "public I{0} Parent{0} {{ get; set;}}", GetClassName(parentTable));
            //    else
            //    {
                    
                    
            //    }
                    
            //}

            



            sb.AppendLine(1, "}");
            sb.AppendLine(0, "}");

            return sb.ToString();
        }
    }



    


    
    [Implements(typeof(ITypeGenerator),ServiceName = "InterfaceGenerator")]
    public class InterfaceBuilder : TypeGeneratorBase
    {
        
        public override string Generate(TableInfo tableInfo)
        {
            var sb = new StringBuilder();

            sb.AppendLine(0,"using System;");
            sb.AppendLine(0,"using System.Collections.Generic;");
            sb.AppendLine();
            sb.AppendLine(0, "namespace {0}", Namespace);
            sb.AppendLine(0, "{");
            sb.AppendLine(1, "public partial interface I{0} ", GetClassName(tableInfo));
            sb.AppendLine(1, "{");

            

            var foreignKeyColumns =
                tableInfo.ForeignKeys.SelectMany(fk => fk.Columns).Select(fkc => fkc.ForeignColumnInfo).Except(
                    tableInfo.PrimaryKey.Columns);
            foreach (var column in tableInfo.Columns.Values.Except(foreignKeyColumns).OrderBy(c => c.OrdinalPosition))
            {
                if (column.ColumnName == GetClassName(tableInfo))
                    sb.AppendLine(2, "{0} {1}{2} {{ get; set; }}", GetDataTypeName(column.DataType), column.ColumnName,column.OrdinalPosition);                
                else
                    sb.AppendLine(2, "{0} {1} {{ get; set; }}", GetDataTypeName(column.DataType), column.ColumnName);                
            }




            //Child relations 
            var childRelations = tableInfo.Relations.Select(r => r.ForeignKey).Except(tableInfo.ForeignKeys).GroupBy(c => c.Table);
            foreach (var childRelation in childRelations)
            {
                if (childRelation.Count() == 1)
                {
                    sb.AppendLine(2, "IList<I{0}> {1} {{ get; }}", GetClassName(childRelation.Key),
                                  GetTableName(childRelation.Key));
                }
            }


            foreach (var foreignKey in tableInfo.ForeignKeys)
            {
                //handle singular keys
                if (foreignKey.Columns.Count == 1)
                {
                    var foreignKeyColumnInfo = foreignKey.Columns.First();
                    var propertyName = foreignKeyColumnInfo.ForeignColumnInfo.ColumnName;
                    var primaryKeyColumnName = foreignKey.Columns.First().PrimaryColumnInfo.ColumnName;
                    if (propertyName != primaryKeyColumnName)
                    {
                        //if (propertyName.Contains(primaryKeyColumnName))
                        //    propertyName = propertyName.Replace(primaryKeyColumnName, GetClassName(foreignKey.PrimaryTable));

                        if (propertyName.Contains(primaryKeyColumnName))
                            propertyName = propertyName.Replace(primaryKeyColumnName, GetClassName(foreignKey.PrimaryTable));
                        else
                            propertyName = GetClassName(foreignKey.PrimaryTable);
                    }
                    else
                    {
                        propertyName = GetClassName(foreignKey.PrimaryTable);
                        if (tableInfo == foreignKey.PrimaryTable)
                            propertyName = string.Format("Parent{0}", propertyName);
                    }

                    var propertyString = string.Format("I{0} {1} {{ get; set;}}", GetClassName(foreignKey.PrimaryTable), propertyName);
                    sb.AppendLine(2, propertyString);
                }
                else
                {
                    sb.AppendLine(2, "I{0} {0} {{ get; set;}}", GetClassName(foreignKey.PrimaryTable));
                }
            }



            ////Parent relations
            //var parentTables = tableInfo.ForeignKeys.Select(fk => fk.PrimaryTable).Distinct();
            //foreach (var parentTable in parentTables)
            //{

            //    if (parentTable == tableInfo)
            //        sb.AppendLine(2, "I{0} Parent{0} {{ get; set;}}", GetClassName(parentTable));
            //    else
            //        sb.AppendLine(2, "I{0} {0} {{ get; set;}}", GetClassName(parentTable));
            //}
                        

            sb.AppendLine(1, "}");
            sb.AppendLine(0, "}");
            
            

            return sb.ToString();                        

        }

      

        

    }
        
    
}
